package com.yunqian.web;

import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.java.emoji.EmojiConverter;
import com.yunqian.constant.RedDotKey;
import com.yunqian.exception.ActionException;
import com.yunqian.redot.constant.OrderStatus;
import com.yunqian.redot.domain.CoursesOrder;
import com.yunqian.redot.domain.Customers;
import com.yunqian.redot.domain.TicketOrder;
import com.yunqian.redot.domain.WechatPay;
import com.yunqian.redot.service.CoursesOrderService;
import com.yunqian.redot.service.CustomersService;
import com.yunqian.redot.service.TicketOrderService;
import com.yunqian.redot.service.WechatPayService;
import com.yunqian.util.validate.Validator;
import com.yunqian.util.wechat.SignatureUtil;
import com.yunqian.util.wechat.WechatPayUtil;
import com.yunqian.util.wechat.WechatUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.Optional;

/**
 * @author czy
 * @describe
 * @date 2018/10/10
 */
@Log4j2
@RestController
@RequestMapping("/api/wechat")
public class WechatController {

    @Autowired
    private CustomersService customersService;
    @Autowired
    private CoursesOrderService coursesOrderService;
    @Autowired
    private TicketOrderService ticketOrderService;
    @Autowired
    private WechatPayService wechatPayService;

    private EmojiConverter emojiConverter = EmojiConverter.getInstance();

    @RequestMapping("/handler")
    public void processPost(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 签名
        String signature = request.getParameter("signature");
        // 时间戳
        String timestamp = request.getParameter("timestamp");
        // 随机数
        String nonce = request.getParameter("nonce");
        // 通过检验signature对请求进行校验，若校验成功则原样返回echostr，表示接入成功，否则接入失败
        if (SignatureUtil.checkSignature(signature, timestamp, nonce)) {
            // 随机字符串
            String echostr = request.getParameter("echostr");
            response.getWriter().write(echostr);
        }
    }

    /**
     * 微信网页授权
     */
    @PostMapping("/auth")
    public Customers accessToken(@RequestBody Map<String, Object> params) {
        // 获取access_token
        String code = Optional.ofNullable((String) params.get("code")).orElseThrow(() -> new ActionException("微信code不允许为空"));
        JSONObject convertValue = WechatUtil.getOauth2AccessToken(RedDotKey.APP_ID, RedDotKey.APP_SECRET, code);
        String accessToken = convertValue.getString("access_token");
        String openId = convertValue.getString("openid");
        // 拉取用户信息
        JSONObject userInfo = WechatUtil.getUserInfo(accessToken, openId);
        Customers customers = new Customers();
        customers.setOpenId(userInfo.getString("openid"));
        customers.setName(emojiConverter.toUnicode(userInfo.getString("nickname")));
        customers.setGender(Integer.parseInt(userInfo.getString("sex")));
        customers.setProvince(userInfo.getString("province"));
        customers.setCity(userInfo.getString("city"));
        customers.setCountry(userInfo.getString("country"));
        customers.setAvatarUrl(userInfo.getString("headimgurl"));
        return customersService.save(customers);
    }


    /**
     * 支付回调 不允许带参数
     * https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
     *
     * @param request
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "/notify")
    public void callBack(HttpServletRequest request, HttpServletResponse response) {
        log.warn("call back execute");
        // 回调信息
        Map<String, String> map = WechatUtil.callBack(request);
        // 订单编号
        String outTradeNo = map.get("out_trade_no");
        // 订单金额(分)
        String totalFee = map.get("total_fee");
        // 商家数据包 course_order : 课程订单   ticket_order：购票订单
        String attach = map.get("attach");
        if ("course_order".equals(attach)) {
            CoursesOrder order = coursesOrderService.findByOrderNo(outTradeNo);
            // 检查订单是否存在 如果不存在则无需继续回调  判断状态 如果不为待付款状态则直接返回
            if (Validator.isNull(order) || !Validator.equals(order.getStatus(), OrderStatus.NEW.getCode())) {
                out("SUCCESS", response);
                return;
            }
            // 实际订单金额(分)
            String totalPrice = String.valueOf(order.getPrice().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_DOWN));
            // 签名验证,并校验返回的订单金额是否与商户的订单金额一致
            if (checkSign(map) && totalFee.equals(totalPrice)) {
                String resultCode = map.get("result_code");
                // 交易成功则更新订单状态
                if (Validator.equals("SUCCESS", resultCode)) {
                    order.setStatus(OrderStatus.SUCCESS.getCode());
                    order.setPayTime(new Date());
                    coursesOrderService.update(order);
                }
                // 保存回调结果
                WechatPay notify = wechatPayService.getByOutTradeNo(outTradeNo);
                notify.setResultCode(map.get("result_code"));
                notify.setErrCode(map.get("err_code"));
                notify.setOpenid(map.get("openid"));
                notify.setTotalFee(Integer.valueOf(map.get("total_fee")));
                notify.setTransactionId(map.get("transaction_id"));
                notify.setTimeEnd(map.get("time_end"));
                wechatPayService.update(notify);
                out("SUCCESS", response);
            } else {
                out("FAIL", response);
            }
        } else if ("ticket_order".equals(attach)) {
            TicketOrder order = ticketOrderService.findByOrderNo(outTradeNo);
            // 检查订单是否存在 如果不存在则无需继续回调  判断状态 如果不为待付款状态则直接返回
            if (Validator.isNull(order) || !Validator.equals(order.getStatus(), OrderStatus.NEW.getCode())) {
                out("SUCCESS", response);
                return;
            }
            // 实际订单金额(分)
            String totalPrice = String.valueOf(order.getTotalPrice().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_DOWN));
            // 签名验证,并校验返回的订单金额是否与商户的订单金额一致
            if (checkSign(map) && totalFee.equals(totalPrice)) {
                String resultCode = map.get("result_code");
                // 交易成功则更新订单状态
                if (Validator.equals("SUCCESS", resultCode)) {
                    order.setStatus(OrderStatus.SUCCESS.getCode());
                    order.setPayTime(new Date());
                    ticketOrderService.update(order);
                }
                // 保存回调结果
                WechatPay notify = wechatPayService.getByOutTradeNo(outTradeNo);
                notify.setResultCode(map.get("result_code"));
                notify.setErrCode(map.get("err_code"));
                notify.setOpenid(map.get("openid"));
                notify.setTotalFee(Integer.valueOf(map.get("total_fee")));
                notify.setTransactionId(map.get("transaction_id"));
                notify.setTimeEnd(map.get("time_end"));
                wechatPayService.update(notify);
                out("SUCCESS", response);
            } else {
                out("FAIL", response);
            }
        } else {
            out("SUCCESS", response);
            throw new ActionException("数据包信息错误");
        }
    }

    /**
     * 签名验证
     *
     * @param map
     * @return
     */
    private boolean checkSign(Map<String, String> map) {
        // API 返回的sign
        String signFromAPIResponse = map.get("sign");
        if (signFromAPIResponse == "" || signFromAPIResponse == null) {
            log.warn("api response sign is null");
            return false;
        }
        // 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
        map.put("sign", "");
        // 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
        String signForAPIResponse = getSign(map, RedDotKey.MCH_SECRET);
        if (!signForAPIResponse.equals(signFromAPIResponse)) {
            //签名验证不通过，表示这个API返回的数据有可能已经被篡改了
            log.warn("api response sign mismatching:" + signForAPIResponse + "///" + signFromAPIResponse);
            return false;
        }
        log.warn("api response sign matching");
        return true;
    }

    /**
     * 应答
     *
     * @param return_code
     * @param response
     * @throws IOException
     */
    private void out(String return_code, HttpServletResponse response) {
        String xml = "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
        // 通过流的方式将结果返回给微信
        BufferedOutputStream out = null;
        try {
            out = new BufferedOutputStream(response.getOutputStream());
            out.write(xml.getBytes());
            out.flush();
        } catch (IOException e) {
            log.warn("The reply message XML writes to the IO stream exception", e);
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 签名
     *
     * @param map
     * @param key
     * @return
     */
    private String getSign(Map<String, String> map, String key) {
        // 去掉空值跟签名参数(空值不参与签名，所以需要去掉)
        Map<String, String> sParaTemp = WechatPayUtil.paraFilter(map);
        // 对参数按照key=value的格式，并按照参数名ASCII字典序排序
        String mapStr = WechatPayUtil.createLinkString(sParaTemp);
        // MD5运算生成签名
        String sign = WechatPayUtil.sign(mapStr, key, "utf-8").toUpperCase();
        return sign;
    }
}
